﻿
#define SPADAS_DEPRECATED_HIDE
#include "NextMJpegEncoderCPU.h"
#include "ffmpegdl.h"

namespace videonext
{
	class MJpegEncoderCPUVars : public Vars
	{
	public:
		Pointer encoder;
		Pointer frame;
		Pointer packet;
		Pointer bgr2yuv420p;
		Size2D size;
	};
}

using namespace videonext;

MJpegEncoderCPU::MJpegEncoderCPU() : Object<MJpegEncoderCPUVars>(new MJpegEncoderCPUVars, TRUE)
{
	vars->encoder = 0;
	vars->frame = 0;
	vars->packet = 0;
	vars->bgr2yuv420p = 0;
}
Bool MJpegEncoderCPU::open(Size2D frameSize, UInt qscale)
{
	close();

	Pointer targetCodec = avcodec_find_encoder_by_name("mjpeg");
	if (!targetCodec) return FALSE;

	vars->encoder = avcodec_alloc_context3(targetCodec);
	if (!vars->encoder) return FALSE;

	avcodec_set_bit_rate(vars->encoder, 0);
	avcodec_set_width(vars->encoder, frameSize.width);
	avcodec_set_height(vars->encoder, frameSize.height);
	avcodec_set_time_base(vars->encoder, 1, 30);
	avcodec_set_framerate(vars->encoder, 30, 1);
	avcodec_set_pix_fmt(vars->encoder, AVPixelFormat::AV_PIX_FMT_YUVJ420P);
	avcodec_set_qmin(vars->encoder, (Int)qscale);
	avcodec_set_qmax(vars->encoder, (Int)qscale);

	Int resultOpenCodec = avcodec_open2(vars->encoder, targetCodec, NULL);
	if (resultOpenCodec < 0) return FALSE;

	vars->frame = av_frame_alloc();
	if (!vars->frame) return FALSE;

	av_frame_set_format(vars->frame, avcodec_get_pix_fmt(vars->encoder));
	av_frame_set_width(vars->frame, avcodec_get_width(vars->encoder));
	av_frame_set_height(vars->frame, avcodec_get_height(vars->encoder));

	Int resultCreateFrameData = av_frame_get_buffer(vars->frame, getFFMPEGLibraryVersion() >= 3 ? 0 : 8);
	if (resultCreateFrameData < 0) return FALSE;

	vars->bgr2yuv420p = sws_getContext(frameSize.width, frameSize.height, AVPixelFormat::AV_PIX_FMT_BGR24, frameSize.width, frameSize.height, AVPixelFormat::AV_PIX_FMT_YUV420P, SWS_POINT, NULL, NULL, NULL);
	if (!vars->bgr2yuv420p) return FALSE;

	if (getFFMPEGLibraryVersion() >= 3)
	{
		vars->packet = av_packet_alloc();
		if (!vars->packet) return FALSE;
	}
	else
	{
		vars->packet = av_mallocz_av_packet();
		if (!vars->packet) return FALSE;
		av_init_packet(vars->packet);
	}

	vars->size = frameSize;

	return TRUE;
}
void MJpegEncoderCPU::close()
{
	if (vars->encoder) avcodec_free_context(&vars->encoder);
	if (vars->frame) av_frame_free(&vars->frame);
	if (vars->packet)
	{
		if (getFFMPEGLibraryVersion() >= 3)
		{
			av_packet_free(&vars->packet);
		}
		else
		{
			av_free_packet(vars->packet);
			vars->packet = NULL;
		}
	}
	if (vars->bgr2yuv420p)
	{
		sws_freeContext(vars->bgr2yuv420p);
		vars->bgr2yuv420p = 0;
	}
}
Binary MJpegEncoderCPU::encode(CompatibleImage image)
{
	if (vars->encoder == 0 || vars->frame == 0 || vars->packet == 0 || vars->bgr2yuv420p == 0) return Binary();
	if (image.width() != vars->size.width || image.height() != vars->size.height) return Binary();
	if (image.getType() != CompatibleImageType::ImagePointerColor && image.getType() != CompatibleImageType::IplImageColor && image.getType() != CompatibleImageType::AVFrameYUV420P) return Binary();

	Pointer targetFrame = NULL;
	if (image.getType() == CompatibleImageType::AVFrameYUV420P)
	{
		targetFrame = image.getImage();
	}
	else
	{
		CompatibleChannelData channelData = image.channels()[0];

		Byte *bgrSource[3] = { channelData.data(), NULL, NULL };
		Int bgrStride[3] = { (Int)channelData.step(), 0, 0 };
		sws_scale(vars->bgr2yuv420p, bgrSource, bgrStride, 0, vars->size.height, av_frame_get_data(vars->frame), av_frame_get_linesize(vars->frame));

		targetFrame = vars->frame;
	}

	if (getFFMPEGLibraryVersion() >= 3)
	{
		Int resultSendFrame = avcodec_send_frame(vars->encoder, targetFrame);
		if (resultSendFrame < 0) return Binary();

		Int resultReceive = avcodec_receive_packet(vars->encoder, vars->packet);
		if (resultReceive < 0) return Binary();
	}
	else
	{
		Int gotPacket = 0;
		Int resultEncode = avcodec_encode_video2(vars->encoder, vars->packet, targetFrame, &gotPacket);
		if (resultEncode < 0 || gotPacket == 0) return Binary();
	}

	Binary output(av_packet_get_data(vars->packet), av_packet_get_size(vars->packet));
	av_packet_unref(vars->packet);

	return output;
}

#if defined(VIDEONEXT_WITH_OPENCV)
Binary videonext::encodeJpegImage(CompatibleImage image, Int quality)
{
	Array<CompatibleChannelData> channels = image.channels();
	if (channels.size() != 1) return Binary();

	IplImage iplImage;
	if (!channels[0].iplImage(iplImage)) return Binary();

	int params[3];
	params[0] = cv::IMWRITE_JPEG_QUALITY;
	params[1] = math::clamp(quality, 50, 100);
	params[2] = -1;
	CvMat *data = cvEncodeImage(".jpg", &iplImage, params);

	Binary output(data->cols * data->rows);
	memcpy(output.data(), data->data.ptr, output.size());
	cvReleaseMat(&data);

	return output;
}
#endif